package cn.moefly.common.component;

import cn.moefly.common.annotation.AsyncEventListener;
import cn.moefly.common.helper.SpringContextHelper;
import cn.moefly.common.utils.ThreadPoolUtils;
import cn.moefly.common.utils.YamlUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationListenerMethodAdapter;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ExecutorConfigurationSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * 配置{@link AsyncEventListener}注解使用开启异步事件广播
 *
 * use {@link AsyncEventListener}Annotation using open asynchronous event broadcast
 *
 * When event-driven, create your own message object for all specific messages and broadcast using springContext
 * springContext is available at {@link com.casa.aems.common.helper.SpringContextHelper}
 *
 * There are 3 ways to monitor events
 * 1. Inherit {@link ApplicationListener}
 * 2. Spring provides annotations {@link org.springframework.context.event.EventListener}
 *
 * 3. Custom asynchronous processing annotation {@link com.casa.aems.common.annotation.AsyncEventListener}
 */
@Slf4j
@Component(value = "applicationEventMulticaster")
public class AsyncApplicationEventMulticaster extends SimpleApplicationEventMulticaster implements BeanPostProcessor {

    private final Map<ApplicationListener, String> mTargetExecutorMap = new HashMap<>();

    private final HashMap<String, TaskExecutor> mExecutorMap = new HashMap<>();

    private final static String DEFAULT_EVENT_SYNC_EXECUTOR_NAME = "&sync";

    private final static String EVENT_THREAD_POOL_SETTING_PREFIX = "common.eventThreadPool";



    @Override
    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        if (listener instanceof ApplicationListenerMethodAdapter){
            String executorName = mTargetExecutorMap.get(listener);
            if (executorName == null){
                synchronized (mTargetExecutorMap){
                    try {
                        Field methodField = ApplicationListenerMethodAdapter.class.getDeclaredField("method");
                        methodField.setAccessible(true);
                        var method = (Method)methodField.get(listener);
                        if (method.isAnnotationPresent(AsyncEventListener.class)){
                            AsyncEventListener asyncEventListener = method.getAnnotation(AsyncEventListener.class);
                            mTargetExecutorMap.put(listener,asyncEventListener.value());
                        } else {
                            //对于不是Async的Event采取同步调用
                            // Take a synchronous call for an event that is not Async
                            mTargetExecutorMap.put(listener,DEFAULT_EVENT_SYNC_EXECUTOR_NAME);
                        }
                    } catch (NoSuchFieldException | IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }

            var executor = mExecutorMap.get(executorName);
            if (executor == null){
                executor = mExecutorMap.get(DEFAULT_EVENT_SYNC_EXECUTOR_NAME);
            }
            executor.execute(()->super.invokeListener(listener, event));
        } else {
            super.invokeListener(listener, event);
        }

    }

    /**
     * 注入内部已经存在的执行器
     * Autowired existing actuators
     */
    @Autowired
    public void autowiredExistedExecutorBeans(@Autowired(required = false) List<ThreadPoolTaskExecutor> executors){
        if (executors == null){return;}

        final Field field;
        try {
            field = ExecutorConfigurationSupport.class.getDeclaredField("beanName");
            field.setAccessible(true);
            executors.forEach(e->{
                try {
                    var beanName = (String)field.get(e);
                    addExecutors(beanName,e);
                } catch (IllegalAccessException ex) {
                    ex.printStackTrace();
                }
            });
        } catch (NoSuchFieldException ignore) {
            log.error("",ignore);
        }
    }

    /**
     * 从配置文件中初始化执行器
     * Initialize the actuator from the configuration file
     */
    @PostConstruct
    public void initPropertiesExecutors(){
        //default sync excutors
        addExecutors(DEFAULT_EVENT_SYNC_EXECUTOR_NAME, Runnable::run);

        Map<String,Map<String,?>> threadPoolSettingMap = YamlUtils.readProperties("application.yml",EVENT_THREAD_POOL_SETTING_PREFIX);
        if (threadPoolSettingMap != null) {
            threadPoolSettingMap.forEach((name, setting) ->
                    addExecutors(name, ThreadPoolUtils.ofSettingMap(setting, "EventPool-" + name + "-")));
        }

        // If default pool does not exist then add
        mExecutorMap.computeIfAbsent("default", k -> {
            var pool = new ThreadPoolTaskExecutor();
            pool.setCorePoolSize(1);
            pool.setMaxPoolSize(1);
            pool.setThreadNamePrefix("EventPool-" + k + "-");
            pool.initialize();
            return pool;
        });

    }


    /**
     * 检查所有bean看看有没有不存在的线程池
     * Check all beans for non-existent thread pools
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Method[] methods = bean.getClass().getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(AsyncEventListener.class)){
                AsyncEventListener asyncEventListener = method.getAnnotation(AsyncEventListener.class);
                var executorName = asyncEventListener.value();
                if (!mExecutorMap.containsKey(executorName)){
                    throw new IllegalArgumentException("AsyncEventListener has an unknown executor : [ "+executorName+" ]!");
                }
            }
        }
        return bean;
    }

    /**
     * 如果想要添加自定义的线程池，请在init-method（PostConstruct）生命周期之前,调用此方法
     * If you want to add a custom thread pool, call this method before the init-method (PostConstruct) life cycle
     */
    public void addExecutors(String name, TaskExecutor executor){
        synchronized (mExecutorMap){
            var orgin = mExecutorMap.get(name);
            if (orgin == null){
                mExecutorMap.put(name, executor);
            } else {
                throw new IllegalArgumentException("the executor: [ "+ name +" ] has exist");
            }
        }
    }


}
